package org.cainiao.oauth2.client.core.entity;

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
import org.cainiao.common.dao.IdBaseEntity;
import org.cainiao.oauth2.client.core.entity.typehandler.*;
import org.cainiao.oauth2.client.core.entity.typehandler.tool.ConfigurationMetadataMap;
import org.cainiao.oauth2.client.core.entity.typehandler.tool.ScopeSet;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrations;
import org.springframework.security.oauth2.core.AuthenticationMethod;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.util.StringUtils;

import java.io.Serial;

/**
 * <br />
 * <p>
 * Author: Cai Niao(wdhlzd@163.com)<br />
 */
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("oauth2_client_registration")
@Schema(name = "CnClientRegistration", description = "当前客户端到某个授权服务器的一个注册信息")
public class CnClientRegistration extends IdBaseEntity {

    @Serial
    private static final long serialVersionUID = 7194103823301276843L;

    private String registrationId;

    private String clientId;

    private String clientSecret;

    @TableField(value = "authorization_grant_type", typeHandler = AuthorizationGrantTypeTypeHandler.class)
    private AuthorizationGrantType authorizationGrantType;

    private String clientName;

    private String redirectUri;

    @TableField(typeHandler = ScopeSetTypeHandler.class)
    private ScopeSet scopes;

    @TableField(typeHandler = ClientAuthenticationMethodTypeHandler.class)
    private ClientAuthenticationMethod clientAuthenticationMethod;

    private String authorizationUri;

    private String tokenUri;

    @TableField("user_info_uri")
    @Schema(description = "用户信息 URI")
    private String uri;

    @TableField(value = "user_info_authentication_method", typeHandler = AuthenticationMethodTypeHandler.class)
    @Schema(description = "用户信息鉴权方法")
    private AuthenticationMethod authenticationMethod;

    private String userNameAttributeName;

    private String jwkSetUri;

    private String issuerUri;

    @TableField(typeHandler = ConfigurationMetadataMapTypeHandler.class)
    private ConfigurationMetadataMap configurationMetadata;

    /**
     * 是否从供应商 OIDC 配置端点拉取 OAuth2 注册信息
     */
    private boolean fromIssuerLocation;

    public ClientRegistration toRegisteredClient() {
        String registrationId_ = getRegistrationId();

        ClientRegistration.Builder builder = isFromIssuerLocation()
            /*
             * 如果配置 fromIssuerLocation("http://127.0.0.1:9000/issuer") 则会依次查询：
             * http://127.0.0.1:9000/issuer/.well-known/openid-configuration
             * http://127.0.0.1:9000/.well-known/openid-configuration/issuer
             * http://127.0.0.1:9000/.well-known/oauth-authorization-server/issuer
             * 在第一个返回 200 响应时停止
             */
            ? ClientRegistrations.fromIssuerLocation(getIssuerUri()).registrationId(registrationId_)
            : ClientRegistration.withRegistrationId(registrationId_)
            .authorizationGrantType(getAuthorizationGrantType())
            .clientAuthenticationMethod(getClientAuthenticationMethod())
            .authorizationUri(getAuthorizationUri())
            .tokenUri(getTokenUri())
            .userInfoUri(getUri())
            .userInfoAuthenticationMethod(getAuthenticationMethod())
            .jwkSetUri(getJwkSetUri())
            .issuerUri(getIssuerUri())
            .providerConfigurationMetadata(getConfigurationMetadata());
        String redirectUri_ = getRedirectUri();
        if (StringUtils.hasText(redirectUri_)) {
            /*
             * 即便 isFromIssuerLocation，仍然建议从数据库中获取 redirectUri
             * 否则会默认设置为占位符：{baseUrl}/{action}/oauth2/code/{registrationId}
             * 而在反向代理等问题的影响下，动态获取的 baseUrl 通常是不对的，为了避免麻烦，建议就用数据库中存储的值
             * 在数据库中，{action}与{registrationId}仍然可以用占位符
             * 反正这个值在任何 OAuth2 流程中都是对客户端开发者公开的
             * 但为了灵活性，仍然做了空值判断
             * 即在 isFromIssuerLocation 情况下想要用占位符模式的 redirectUri，只要不在数据库中存这个字段即可
             */
            builder.redirectUri(redirectUri_);
        }
        String userNameAttributeName_ = getUserNameAttributeName();
        if (StringUtils.hasText(userNameAttributeName_)) {
            /*
             * 即便 isFromIssuerLocation，仍然从数据库中获取 userNameAttributeName
             * 否则会默认设置为：IdTokenClaimNames.SUB（也就是说并不是从 issuer 动态获取的）
             */
            builder.userNameAttributeName(userNameAttributeName_);
        }
        return builder
            .clientName(getClientName())
            .clientId(getClientId())
            .scope(getScopes())
            .clientSecret(getClientSecret()).build();
    }
}
